from utils.idandmeta import IdAndMeta

from stacking.warehouse.component import Component

            
class Crane(IdAndMeta, Component):
    """Cranes are used to execute actions, manipulating items in a warehouse, e.g. picking up one 
    or more items, relocating them to other stacks, or delivering them on pallets. This object 
    contains the internal state of a crane, consisting of a list of items and source locations, 
    and the place where the crane is at."""
    def __init__(self, id, **meta):
        IdAndMeta.__init__(self, id, **meta)
        Component.__init__(self)
        self.items   = []     # [Item] - list of Item objects
        self.sources = []     # [gid] - gid of the stack where each item came from
        self.place   = None   # gid - gid of the place where the crane currently is
        
    def __info__(self):
        place = "???" if self.place is None else self.place
        items = " ".join("i%s" % item.id for item in self.items)
        return "%s at %s <%s>" % (self.id, place, items)
        
    def deepcopy(self):
        clone         = type(self)(self.id, **self.meta)
        clone.items   = list(self.items)
        clone.sources = list(self.sources)
        clone.place   = self.place
        return clone
        
    @property
    def bottom(self):
        return self.items[-1]
        
    def _set_parent(self, warehouse):
        if self.parent is not None:
            for item in self.items:
                self.parent.set_location(item, None)
        Component._set_parent(self, warehouse)
        if warehouse is not None:
            for item in self.items:
                warehouse.set_location(item, self)
                
    def insert(self, index, item, source=None):
        self.items.insert(index, item)
        self.sources.insert(index, source)
        if self.parent is not None:
            self.parent.set_location(item, self)
            
    def append(self, item, source=None):
        self.items.append(item)
        self.sources.append(source)
        if self.parent is not None:
            self.parent.set_location(item, self)
            
    def pop(self):
        item = self.items.pop()
        source = self.sources.pop()
        if self.parent is not None:
            self.parent.set_location(item, None)
        return item, source
        
    def clear(self):
        while len(self.items) > 0:
            self.pop()
        
    def execute(self, action):
        """Executes an action in a warehouse. Returns the number of relocations done while 
        executing the action (this is always 0 for pickups and >= 0 for putdowns)."""
        self.place  = action.place  # (gid)
        items       = self.parent.items
        place       = self.parent.objects[action.place]
        relocations = 0
        # execute a pickup action
        if action.is_pickup:
            source = action.place if place.is_stack else None
            for id in action.items:
                item = items[id]
                if place.is_stack:
                    top = place.pop()
                    assert item is top
                self.append(item, source)
        # execute a putdown action
        elif action.is_putdown:
            for id in action.items:
                item = items[id]
                bottom, source = self.pop()
                assert item is bottom
                if place.is_stack:
                    if source is not None:
                        relocations += 1
                    place.append(item)
        # otherwise the action is unrecognizable
        else:
            raise ValueError("invalid action type")
        return relocations
        
        
